package com.example.edu.Utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.edu.Dto.HotDto;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.example.edu.Utils.Constants.CACHE_NULL_TTL;
import static com.example.edu.Utils.Constants.LOCK;

@Component
public class CacheDataBaseUtil {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    final Random random = new Random();

    @Async
    protected <R,T> void selectAndRebuildAsync(String pre_key,T id,Function<T,R> db_function, Long time, TimeUnit unit){
        String key = pre_key + id;
        R r = db_function.apply(id);
        // 存在该数据，缓存重建并返回
        if (r != null){
            time = unit.toSeconds(time) + random.nextInt(61); // 解决缓存雪崩
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),time,TimeUnit.SECONDS);
        }
        // 否则加入空值
        stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
        // 释放锁
        stringRedisTemplate.delete(LOCK+key);
    }

    protected <R,T> R selectAndRebuild(String key,T id,Function<T,R> db_function, Long time, TimeUnit unit){
        R r = db_function.apply(id);
        // 存在该数据，缓存重建并返回
        if (r != null){
            time = unit.toSeconds(time) + random.nextInt(61); // 解决缓存雪崩
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),time,TimeUnit.SECONDS);
            return r;
        }
        // 否则加入空值并返回
        stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
        return null;
    }

//    protected <R,T> List<R> selectAndRebuildMany(String key,T id,Function<T,List<R>> db_function, Long time, TimeUnit unit){
//        List<R> rs = db_function.apply(id);
//        // 存在该数据，缓存重建并返回
//        if(rs != null){
//            time = unit.toSeconds(time) + random.nextInt(61); // 解决缓存雪崩
//            for (R r : rs)
//                stringRedisTemplate.opsForList().rightPush(key,JSONUtil.toJsonStr(r));
//            stringRedisTemplate.expire(key,time,TimeUnit.SECONDS);
//            return rs;
//        }
//        // 否则加入空值并返回
//        stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
//        return null;
//    }

    protected <R> List<R> selectAndRebuildMany(String key,Supplier<List<R>> db_function,Long time,TimeUnit unit){
        List<R> rs = db_function.get();
        for (R r : rs)
            stringRedisTemplate.opsForList().rightPush(key,JSONUtil.toJsonStr(r));
        stringRedisTemplate.expire(key,time,unit);
        return rs;
    }

    private <D,R> void setFieldValue(D data, String cacheData, String field_name,Class<R> value_type) {
        try {
            Field field = data.getClass().getDeclaredField(field_name);  // 动态获取指定的字段
            field.setAccessible(true);  // 设置字段为可访问
            field.set(data, JSONUtil.toBean(cacheData,value_type));  // 设置缓存中的数据到字段
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.out.println(e);  // 处理字段没有的异常
        }
    }

    public <R,T> R selectById(String pre_key, T id, Class<R> return_type, Function<T,R> db_function, Long time, TimeUnit unit){
        String key = pre_key + id;
        String data_json = stringRedisTemplate.opsForValue().get(key);
        // 检查是否为空字符串
        if(data_json != null && data_json.isEmpty()){
            return null;
        }
        // 不是空也不是null直接返回
        if(data_json != null){
            return JSONUtil.toBean(data_json,return_type);
        }
        // 否则操作数据库
        return selectAndRebuild(key,id,db_function,time,unit);
    }

//    public <R,T> List<R> selectManyById(String pre_key, T id, Class<R> return_type, Function<T,List<R>> db_function, Long time, TimeUnit unit){
//        String key = pre_key + id;
//        String check = stringRedisTemplate.opsForValue().get(key);
//        // 如果check为空字符串则表示没有，直接返回
//        if(check != null && check.isEmpty()){
//            return null;
//        }
//        List<String> data_jsons = stringRedisTemplate.opsForList().range(key,0,-1);
//        // 缓存有直接返回
//        if(data_jsons != null){
//            List<R> rs = new ArrayList<>();
//            for (String data_json : data_jsons)
//                rs.add(JSONUtil.toBean(data_json,return_type));
//            return rs;
//        }
//        // 缓存没有操作数据库
//        return selectAndRebuildMany(key,id,db_function,time,unit);
//    }

    public <R> List<R> selectMany(String key, Class<R> return_type, Supplier<List<R>> db_function,Long time, TimeUnit unit){
        List<String> data_jsons = stringRedisTemplate.opsForList().range(key,0,-1);
        if(data_jsons != null){
            List<R> rs = new ArrayList<>();
            for (String data_json : data_jsons)
                rs.add(JSONUtil.toBean(data_json,return_type));
            return rs;
        }
        return selectAndRebuildMany(key,db_function,time,unit);
    }

    public <R,T> R selectHotById(String pre_key, T id, Class<R> return_type, Function<T,R> db_function, Long time, TimeUnit unit){
        String key = pre_key + id;
        String data_json = stringRedisTemplate.opsForValue().get(key);
        // 检查是否为空字符串
        if(data_json != null && data_json.isEmpty()){
            return null;
        }
        // 不是空也不是null查看逻辑过期时间
        if(data_json != null){
            HotDto hot_data = JSONUtil.toBean(data_json, HotDto.class);
            // 未过期直接返回
            if(hot_data.getExpire_time().isAfter(LocalDate.now())) {
                return JSONUtil.toBean((JSONObject) hot_data.getData(), return_type);
            }
            // 过期则返回并异步重建
            if(Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(LOCK + key, "1")))
                selectAndRebuildAsync(key,id,db_function,time,unit);
            return JSONUtil.toBean((JSONObject) hot_data.getData(), return_type);
        }
        // 否则操作数据库
        return selectAndRebuild(key,id,db_function,time,unit);
    }

    public <R,T> boolean isContainsById(String pre_key, T id, Function<T,R> db_function, Long time, TimeUnit unit){
        String key = pre_key + id;
        String data_json = stringRedisTemplate.opsForValue().get(key);
        // 检查是否为空字符串
        if(data_json != null && data_json.isEmpty()){
            return false;
        }
        // 不是空也不是null直接返回
        if(data_json != null){
            return true;
        }
        // 否则操作数据库
        R r = db_function.apply(id);
        // 存在该数据，缓存重建并返回
        if (r != null){
            time = unit.toSeconds(time) + random.nextInt(61); // 解决缓存雪崩
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),time,TimeUnit.SECONDS);
            return true;
        }
        // 否则返回
        return false;
    }

    @Transactional(rollbackFor = Exception.class)
    public <T,D> void insert(String pre_key, T id, D data,Consumer<D> db_function, Long time,TimeUnit unit) throws Exception {
        try{
            String key = pre_key + id;
            db_function.accept(data);
            time = unit.toSeconds(time) + random.nextInt(61);
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(data), time, TimeUnit.SECONDS);
        } catch (Exception e){
            System.out.println(e);
            throw new Exception("添加失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public <T,D,R> List<D> insertAndGetMany(String pre_key, List<T> ids, List<D> data,String field_name_value,Class<R> value_type,String field_name_key,Consumer<List<D>> db_function, Long time,TimeUnit unit) throws Exception {
        try {
            List<D> not_in_cache = new ArrayList<>();
            for(int i = 0;i < data.size();i++){
                String key = pre_key + ids.get(i);
                String o = stringRedisTemplate.opsForValue().get(key);
                if(o != null)
                    setFieldValue(data.get(i),o,field_name_value,value_type);
                else
                    not_in_cache.add(data.get(i));
            }
            db_function.accept(not_in_cache);
            // 把不在缓存的重建了
            for(D d:not_in_cache){
                String key = pre_key + d.getClass().getDeclaredField(field_name_key).get(d);
                stringRedisTemplate.opsForValue().set(key,d.getClass().getDeclaredField(field_name_value).get(d).toString(),time,unit);
            }
            return data;
        } catch (Exception e){
            System.out.println(e);
            throw new Exception("添加失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public <T> void deleteById(String pre_key, T id,Consumer<T> db_function) throws Exception {
        try {
            String key = pre_key + id;
            db_function.accept(id);
            stringRedisTemplate.delete(key);
        }catch (Exception e){
            System.out.println(e);
            throw new Exception("删除失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public <T> void deleteManyById(String pre_key,List<T> ids,Consumer<List<T>> db_function) throws Exception {
        try {
            db_function.accept(ids);
            for (T id : ids){
                String key = pre_key + id;
                stringRedisTemplate.delete(key);
            }
        }catch (Exception e){
            System.out.println(e);
            throw new Exception("删除失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public <T,D> void updateById(String pre_key, T id, D new_data, BiConsumer<T,D> db_function) throws Exception{
        try {
            String key = pre_key + id;
            db_function.accept(id,new_data);
            stringRedisTemplate.delete(key);
        }catch (Exception e){
            System.out.println(e);
            throw new Exception("更新失败");
        }
    }

    public boolean tryLock(String pre_key,String key,Long time,TimeUnit unit){
        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(LOCK + pre_key + key, "1", time, unit));
    }

    public void unlock(String pre_key,String key){
        stringRedisTemplate.delete(LOCK+pre_key+key);
    }
}
